package activity

import (
	"context"
	"encoding/json"
	"fmt"
	"strconv"
	"strings"

	"mall/common/cachex"
	"mall/common/ctxdatax"
	"mall/common/enumx"
	"mall/service/backend/common/utils/pointy"
	"mall/service/backend/rpc/backendclient"
	"mall/service/blog/rpc/blogclient"
	"mall/service/forum/api/internal/lib/forumreaction"
	"mall/service/forum/api/internal/logic/util"
	"mall/service/forum/api/internal/svc"
	"mall/service/forum/api/internal/types"
	"mall/service/forum/rpc/forumclient"

	"github.com/sirupsen/logrus"
	"github.com/zeromicro/go-zero/core/logx"
	"github.com/zeromicro/go-zero/core/threading"
)

type ForumActivityLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func NewForumActivityLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ForumActivityLogic {
	return &ForumActivityLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (l *ForumActivityLogic) ForumActivity(req *types.ForumActivityReq) (resp *types.ForumActivityResp, err error) {
	var page int64 = 1
	if req.Current > 1 {
		page = req.Current
	}

	var pageSize int64
	if req.PageSize > 0 { // 指定，检查最大值
		pageSize = req.PageSize
		if pageSize > l.svcCtx.Config.Query.MaxAmount {
			pageSize = l.svcCtx.Config.Query.MaxAmount
		}
	} else { // 不指定，使用默认值
		pageSize = l.svcCtx.Config.Query.DefaultAmount
	}

	sectionKey := cachex.ForumActivityFeed
	userId := ctxdatax.GetUserId(l.ctx)
	var (
		isEnd     bool
		cacheResp *types.ForumActivityResp
		eagerLoad string
	)
	preload := "forumFeed:Id,ForumId,TopicId,PostId,AuthorId,AuthorName,AuthorAvatar,AuthorLevel,Excerpt,MediaId,MediaUrl,Sticky,CreatedAt"
	fids, err := l.cacheActivityFeed(sectionKey, (page-1)*pageSize, page*pageSize-1)
	if err == nil && len(fids) > 1 { // scene 1: 有缓存
		if fids[len(fids)-1] == -1 {
			isEnd = true
		}
		resp, err := l.activityFeedsByIds(fids, preload, eagerLoad)
		if err != nil {
			return nil, err
		}

		if len(resp.Data) > 0 {
			tmpMap := make(map[int64]*forumclient.FeedInfo)
			var tmpList []*forumclient.FeedInfo
			for _, item := range resp.Data {
				tmpMap[item.Id] = item
			}
			for _, id := range fids {
				if item, exist := tmpMap[id]; exist {
					tmpList = append(tmpList, item)
				}
			}
			resp.Data = tmpList
		}

		cacheResp = l.parseActivityFeed(resp, userId)
		if isEnd {
			cacheResp.HasNextPage = false
			cacheResp.Current = page
			cacheResp.PageSize = pageSize
		} else {
			cacheResp.Current = page
			cacheResp.PageSize = pageSize
		}
	} else { // scene 2: 无缓存
		resp, err := l.svcCtx.ForumRpc.FeedList(l.ctx, &forumclient.FeedListReq{
			Offset:  int32((page-1)*pageSize),
			Limit:   int32(pageSize),
			Preload: preload,
		})
		if err != nil {
			return nil, err
		}

		cacheResp = l.parseActivityFeed(resp, userId)

		// logrus.Info(fmt.Sprintf("ForumActivity cacheResp: %+v", cacheResp))
	}

	threading.GoSafe(func() {
		feeds := cacheResp.List
		if !cacheResp.HasNextPage {
			feeds = append(feeds, &types.FeedInfo{Id: -1}) // 没有更多项的特殊处理
		}
		err = l.addCacheActivityFeed(context.Background(), sectionKey, feeds)
		if err != nil {
			logrus.Info(fmt.Sprintf("ForumActivity addCache err: %+v", err))
		}
	})

	return cacheResp, nil
}

func (l *ForumActivityLogic) cacheActivityFeed(sectionKey string, start int64, stop int64) ([]int64, error) { ////
	pairs, err := l.svcCtx.BizRedis.ZRevRangeWithScoresCtx(l.ctx, sectionKey, start, stop)
	if err != nil {
		logrus.Info(fmt.Sprintf("ForumActivity getActivityFeedCache err: %+v", err))
		return nil, err
	}

	var ids []int64
	for _, pair := range pairs {
		if id, err := strconv.ParseInt(pair.Key, 10, 64); err == nil {
			ids = append(ids, id)
		}
	}

	return ids, nil
}

func (l *ForumActivityLogic) activityFeedsByIds(ids []int64, preload string, eagerLoad string) (*forumclient.FeedListResp, error) {
	var fids []string
	for _, id := range ids {
		if id > 0 {
			fids = append(fids, strconv.FormatInt(id, 10))
		}
	}

	return l.svcCtx.ForumRpc.FeedList(l.ctx, &forumclient.FeedListReq{
		Preload:   preload,
		EagerLoad: eagerLoad,
		FeedIds:   strings.Join(fids, ","),
	})
}

func (l *ForumActivityLogic) addCacheActivityFeed(ctx context.Context, sectionKey string, feeds []*types.FeedInfo) error {
	if len(feeds) == 0 {
		return nil
	}

	for _, feed := range feeds {
		score := feed.StickyScore + feed.LikeCount * enumx.BlogReactionLikeOffset + feed.ReplyCount * enumx.BlogReactionReplyOffset
		if score < 0 {
			score = 0
		}
		_, err := l.svcCtx.BizRedis.ZaddCtx(ctx, sectionKey, int64(score) + feed.Id, strconv.FormatInt(feed.Id, 10))
		if err != nil {
			return err
		}
	}

	return l.svcCtx.BizRedis.ExpireCtx(ctx, sectionKey, l.svcCtx.Config.Query.ExpireTime)
}

func (l *ForumActivityLogic) parseActivityFeed(resp *forumclient.FeedListResp, userId uint64) (*types.ForumActivityResp) {
	var page int64 = 1
	if resp.Offset > 0 {
		page = int64(resp.Offset) / resp.PageSize
		page += 1
	}
	ret := &types.ForumActivityResp{
		Current:         page,
		PageSize:        resp.PageSize,
		HasNextPage:     resp.HasNextPage > 0,
		HasPreviousPage: resp.HasPreviousPage > 0,
	}
	if len(resp.Data) > 0 {
		var topicIds []string
		for _, item := range resp.Data {
			if item.ForumId >= l.svcCtx.Config.Query.DefaultForumId {
				topicIds = append(topicIds, strconv.FormatInt(item.TopicId, 10))
			}
		}
		topicLikeMap := l.getTopicLikeBatch(topicIds, strconv.FormatInt(int64(userId), 10))
		topicReplyMap := l.getTopicReplyBatch(topicIds)

		memberLevelMap := l.getMemberLevelMap()

		for _, item := range resp.Data {
			var feedType string
			var isLiked bool
			var likeCount int64
			var replyCount int64
			switch item.ForumId {
			case l.svcCtx.Config.Query.PhotoForumId:
				feedType = "photo"
			case l.svcCtx.Config.Query.PostForumId:
				feedType = "review"
			default:
				if item.ForumId >= l.svcCtx.Config.Query.DefaultForumId {
					feedType = "question"
					if topicLikeMap != nil {
						if likeStatus, exist := topicLikeMap[item.TopicId]; exist {
							isLiked = likeStatus.Status > 0
							likeCount = likeStatus.Count
						}
					}
					if topicReplyMap != nil {
						if replyStatus, exist := topicReplyMap[item.TopicId]; exist {
							replyCount = replyStatus.Count
						}
					}
				}
			}
			var levelName string
			var levelUrl string
			if memberLevel, exist := memberLevelMap[int64(item.AuthorLevel)]; exist {
				levelName = memberLevel.Name
				levelUrl  = memberLevel.MediaUrl
			}

			ret.List = append(ret.List, &types.FeedInfo{
				Id:              item.Id,
				TopicId:         item.TopicId,
				PostId:          item.PostId,
				AuthorId:        item.AuthorId,
				AuthorName:      item.AuthorName,
				AuthorAvatar:    item.AuthorAvatar,
				AuthorLevel:     item.AuthorLevel,
				AuthorLevelName: levelName,
				AuthorLevelUrl:  levelUrl,
				Excerpt:         item.Excerpt,
				FeedType:        feedType,
				MediaId:         item.MediaId,
				MediaUrl:        item.MediaUrl,
				StickyScore:     item.StickyScore,
				CreatedAt:       util.GetTimeFromNow(item.CreatedAt),
				UpdatedAt:       item.CreatedAt,
				IsLiked:         isLiked,
				LikeCount:       int32(likeCount),
				ReplyCount:      int32(replyCount),
			})
		}
	}

	if page == 1 {
		items, err := l.getTrendingItems()
		if err != nil {
			logrus.Info(fmt.Sprintf("ForumActivity getTrendingItems err: %+v", err))
		} else {
			ret.TrendingItems = items
		}
	}

	return ret
}


func (l *ForumActivityLogic) getTrendingItems() ([]*types.ForumMeta, error) {
	sectionKey := cachex.ForumTrendingFeed
	var pageSize int64 = 10
	pairs, err := l.svcCtx.BizRedis.ZRevRangeWithScoresCtx(l.ctx, sectionKey, 0, pageSize-1)
	if err != nil {
		return nil, err
	}

	var items []*types.ForumMeta
	if len(pairs) > 0 {
		for _, pair := range pairs {
			var item types.ForumMeta
			if err = json.Unmarshal([]byte(pair.Key), &item); err != nil {
				logx.WithContext(l.ctx).Error("ForumActivity TrendingItem Unmarshal err : %+v , val : %s ", err, pair.Key)
			} else {
				if item.Id > 0 {
					items = append(items, &item)
				}
			}
		}
	} else {
		res, err := l.svcCtx.BlogRpc.SearchKeywordList(l.ctx, &blogclient.SearchKeywordListReq{
			Limit:      int32(pageSize),
			PlatformId: int32(enumx.ThirdPartyPlatformWechatMini),
		})
		if err != nil {
			return nil, err
		}

		if len(res.Data) > 0 {
			for _, item := range res.Data {
				items = append(items, &types.ForumMeta{
					Id:    item.Id,
					Label: item.Key,
				})
			}
		}
		if res.HasNextPage == 0 {
			items = append(items, &types.ForumMeta{Id: -1}) // 没有更多项的特殊处理
		}

		threading.GoSafe(func() {
			ctx := context.Background()
			for _, item := range items {
				trendingItem, err := json.Marshal(item)
				if err != nil {
					logrus.Info(fmt.Sprintf("ForumActivity TrendingItem Marshal err: %+v", err))
				}

				_, err = l.svcCtx.BizRedis.ZaddCtx(ctx, sectionKey, item.Id, string(trendingItem))
				if err != nil {
					logrus.Info(fmt.Sprintf("ForumActivity getTrendingItems addCache err: %+v", err))
				}
			}

			var expireTime int = 10800 // 3小时，没有热搜榜，无法刷新，所以缓存时间调低
			err = l.svcCtx.BizRedis.ExpireCtx(ctx, sectionKey, expireTime)
			if err != nil {
				logrus.Info(fmt.Sprintf("ForumActivity getTrendingItems addCache err: %+v", err))
			}
		})
	}

	return items, nil
}

func (l *ForumActivityLogic) getMemberLevelMap() (map[int64]*types.MemberLevelInfo) {
	systemMemberLevelKey := cachex.LocalMemberLevel
	var list []*types.MemberLevelInfo
	resp := make(map[int64]*types.MemberLevelInfo)

	localCacheMemberLevelList, ok := l.svcCtx.LocalCache.Get(systemMemberLevelKey)
	// logrus.Info(fmt.Sprintf("ForumActivity getMemberLevelMap: %+v", localCacheMemberLevelList))
	if ok {
		list = localCacheMemberLevelList.([]*types.MemberLevelInfo)
	} else {
		var pageSize int64 = 50
		res, err := l.svcCtx.BackendRpc.MemberLevelList(l.ctx, &backendclient.MemberLevelListReq{
			PageSize: pageSize,
		})
		if err != nil {
			logrus.Info(fmt.Sprintf("ForumActivity getMemberLevelMap err: %+v", err))
			return resp
		}

		if len(res.List) > 0 {
			for _, item := range res.List {
				list = append(list, &types.MemberLevelInfo{
					Id:                item.Id,
					Name:              item.Name,
					Slug:              item.Slug,
					MediaId:           item.MediaId,
					MediaUrl:          item.MediaUrl,
					IntegralThreshold: item.Integral,
				})
			}
		}

		l.svcCtx.LocalCache.Set(systemMemberLevelKey, list)
	}

	if len(list) > 0 {
		for _, item := range list {
			resp[item.Id] = item
		}
	}

	return resp
}

func (l *ForumActivityLogic) getTopicLikeBatch(topicIds []string, userId string) map[int64]*forumreaction.ForumReactionStatus {
	forumReaction := forumreaction.NewForumReaction(l.ctx, l.svcCtx, cachex.ForumQuestionLike)
	likeMap, err := forumReaction.GetReactionMap(topicIds, userId)
	if err != nil {
		logrus.Info(fmt.Sprintf("ForumActivity getTopicLikeBatch GetReactionMap err: %+v ", err))
		return nil
	}

	// logrus.Info(fmt.Sprintf("ForumReaction questionLikeMap : %+v ", likeMap))
	var objectIds []string
	var extraIds []string
	for i, item := range likeMap {
		// logrus.Info(fmt.Sprintf("ForumReaction questionLikeMap item %d : %+v ", i, item))
		if item.Status == -1 {
			objectIds = append(objectIds, strconv.FormatInt(i, 10))
		}
		if item.Count == -1 {
			extraIds = append(extraIds, strconv.FormatInt(i, 10))
		}
	}
	if len(objectIds) > 0 || len(extraIds) > 0 {
		logMap, extraMap, err := l.getTopicLikeMap(objectIds, userId, extraIds)
		if err != nil {
			logrus.Info(fmt.Sprintf("ForumActivity getTopicLikeMap err: %+v ", err))
			return nil
		}

		for i, item := range likeMap {
			if item.Status == -1 {
				if status, ok := logMap[i]; ok { // 数据库有记录
					item.Status = status
					forumReaction.InitStatus(
						strconv.FormatInt(i, 10),
						userId,
						strconv.FormatInt(int64(status), 10),
					)
				} else {
					item.Status = 0
					forumReaction.InitStatus(strconv.FormatInt(i, 10), userId, "-1")
				}
			}
			if item.Count == -1 {
				if count, ok := extraMap[i]; ok { // 数据库有记录
					item.Count = count
					forumReaction.InitCount(strconv.FormatInt(i, 10), strconv.FormatInt(count, 10))
				} else {
					item.Count = 0
					forumReaction.InitCount(strconv.FormatInt(i, 10), "0")
				}
			}
			likeMap[i] = item
		}
	}

	return likeMap
}

func (l *ForumActivityLogic) getTopicLikeMap(
	topicIds []string,
	userId string,
	extraIds []string,
) (resp map[int64]int32, extra map[int64]int64, err error) {
	resp = make(map[int64]int32)
	extra = make(map[int64]int64)

	if len(topicIds) == 0 && len(extraIds) == 0 {
		return
	}

	var filters []string
	if len(topicIds) > 0 {
		filters = append(filters, "TopicId,in:" + strings.Join(topicIds, ","))
		filters = append(filters, "UserId,equalTo:" + userId)
		filters = append(filters, "Type,equalTo:" + cachex.ForumQuestionLike)
	}
	var preFilters []string
	if len(extraIds) > 0 {
		preFilters = append(preFilters, "TopicId,in:" + strings.Join(extraIds, ","))
		preFilters = append(preFilters, "Type,equalTo:" + cachex.ForumQuestionLike)
	}
	res, err := l.svcCtx.ForumRpc.GetReactionLogList(l.ctx, &forumclient.BaseListReq{
		Preload:         pointy.GetPointer("forumReactionLog:TopicId,UserId,Status,Count"),
		Filter:          pointy.GetPointer(strings.Join(filters, ";")),
		PreFilterEntity: pointy.GetPointer(cachex.ForumQuestionLike),
		PreFilter:       pointy.GetPointer(strings.Join(preFilters, ";")),
	})
	if err != nil {
		return nil, nil, err
	}

	if len(res.List) > 0 {
		for _, v := range res.List {
			if v.TopicId == nil || v.Status == nil || *v.TopicId == 0 || *v.Status == 0 {
				resp[int64(*v.TopicId)] = int32(*v.Status)
			}
		}
	}
	if len(res.Extra) > 0 {
		for _, v := range res.Extra {
			if v.TopicId == nil || v.Count == nil || *v.TopicId == 0 || *v.Count == 0 {
				extra[int64(*v.TopicId)] = int64(*v.Count)
			}
		}
	}

	return
}

func (l *ForumActivityLogic) getTopicReplyBatch(topicIds []string) map[int64]*forumreaction.ForumReactionStatus {
	forumReaction := forumreaction.NewForumReaction(l.ctx, l.svcCtx, cachex.ForumQuestionReply)
	replyMap, err := forumReaction.GetReactionMap(topicIds, "")
	if err != nil {
		logrus.Info(fmt.Sprintf("ForumReaction questionReplyMap err: %+v ", err))
		return nil
	}

	for i, item := range replyMap {
		if item.Count == -1 {
			item.Count = 0
		}
		replyMap[i] = item
	}

	return replyMap
}
